package com.zjq.smartdevice.websocket.handler;

import com.zjq.smartdevice.constants.DeviceStatus;
import com.zjq.smartdevice.events.DevicePowerEvent;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;

import java.io.IOException;
import java.net.URI;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @User zhangjiaqi
 * @Project project
 * @Package com.zjq.smartdevice.websocket.handler
 * @Date 2022/3/24 11:29
 */
@Component
public class PowerCommandHandler extends TextWebSocketHandler {

    private ApplicationContext context;

    @Autowired
    public void setContext(ApplicationContext context) {
        this.context = context;
    }


    private ConcurrentHashMap<String, WebSocketSession> allClients = new ConcurrentHashMap<>();//保存所有连接设备的map
    private ConcurrentHashMap<String,Long> lastHearBeatMap=new ConcurrentHashMap<>();//记录每一次设备最后一次心跳的时间

    /**
     * 建立连接
     * @param session
     * @throws Exception
     */
    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        //获取当前连接的标识
        URI uri = session.getUri();
        String uriString = uri.toString();//获取到字符串格式的地址,地址的最后一位是标识
        String deviceKey = uriString.substring(uriString.lastIndexOf("/") + 1);
        session.getAttributes().put("deviceKey", deviceKey);
        //检查下当前标识有没有之前连接过
        WebSocketSession socketSession = allClients.get(deviceKey);
        if (socketSession != null) {
            //之前连接过,关闭之前的连接,替换为新的连接
            socketSession.close();
        }
        allClients.put("deviceKey", session);//保存会话
        //设备连接之后,在之类更新状态就行了
        //我们要保证的单一职责,更细设备状态的操作要我们当前websocket没关系,我们的websocket主要负责记录设备,主动给设备推送数据
        //交给专门更新的人处理,可以通过事件发送出去, 谁接收谁处理, 谁处理谁接收
        //我们发送事件的目的是更新某个设备为在线状态,并且记录连接的ip和区域,我们需要这些数据, 其中设备的编号,ip只能在这边获取到,所以我们需要通过事件携带出去
        context.publishEvent(new DevicePowerEvent(deviceKey,session.getRemoteAddress().getHostString(), DeviceStatus.ONLINE));
    }

    /**
     * 收到消息
     * @param session
     * @param message
     * @throws Exception
     */
    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
        System.err.println("收到消息");
        //记录当前设备最后一次给我们发送数据的时间,当前设备,第二个是最后一次时间,典型的key-value
        Object deviceKey = session.getAttributes().get("deviceKey");//获取到标识
        lastHearBeatMap.put((String) deviceKey,System.currentTimeMillis());//记录最后一次时间,用于心跳检查用
    }

    /**
     * 连接关闭
     *
     * @param session
     * @param status
     * @throws Exception
     */
    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
        System.err.println("有设备离线了");
        Object deviceKey = session.getAttributes().get("deviceKey");//获取到标识
        //从allcients移除
        allClients.remove(deviceKey);
        //更新状态为离线
        context.publishEvent(new DevicePowerEvent((String) deviceKey, null, DeviceStatus.OFFLINE));
    }

    @EventListener
    public void onEvent(DevicePowerEvent event) {
        System.err.println("设备心跳检查开始了");
        allClients.entrySet().parallelStream().forEach(stringWebSocketSessionEntry -> {
            WebSocketSession socketSession = stringWebSocketSessionEntry.getValue();//当前遍历的设备的会话
            String deviceId = stringWebSocketSessionEntry.getKey();//设备标识
            Long lastHeartbeatTime = lastHearBeatMap.get(deviceId);//获取最后一次心跳
            if (System.currentTimeMillis() - lastHeartbeatTime > 3000) {
                //当前时间减去最后一次心跳时间大于30秒,我们就认为你断线了
                //断线做三个事情,一个是关闭session,一个是从allclients移除设备,一个是从心跳的map中移除设备
                //遍历的时候能直接删除数据吗?
                if (socketSession.isOpen()) {
                    try {
                        socketSession.close();//关闭,触发上面的关闭监听,从map中会移除数据
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                lastHearBeatMap.remove(deviceId);

                    
            }
        });
    }

}
